package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.gateway.constant.RedisConst;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * 自定义全局过滤器
 * 默认情况下有九大过滤器(网关自带)
 * 数越小,执行优先级越高,最小是Integer最小值,最大是Integer最大值
 */
@Component
public class LoginGlobalFilter implements GlobalFilter, Ordered {
    //路径匹配
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Autowired
    private RedisTemplate redisTemplate;
    public static final String REDIRECT_URL = "http://passport.gmall.com/login.html?originUrl=";
    @Value("${gateway.urls}")
    private String[] urls;//  trade.html  pay.html order.html
//    private String[] urls = {"trade.html", " pay.html", "order.html"};

    //全局过滤器的执行方法,只要能发出的请求,都要经过此方法
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath();

        //第一种:cart.gmall.com/.....  浏览器自动拼接上http:  这种方式会节省一点点性能
//        request.getURI().getRawSchemeSpecificPart();
        //第二种 包含http
        String url = request.getURI().toString();
        //1 校验请求路径是否为内部资源 inner开头为内部资源,页面不允许访问,只能内部远程调用使用
        //??????????
        if (antPathMatcher.match("/**/inner/**", path)) {
            //没有权限
            return getVoidMono(response, ResultCodeEnum.PERMISSION);
        }
        //2 校验用户是否登录
        String userId = getUserId(request);
        //3.1 有些路径不登录也能访问
        //3.2 有些路径不登录不能访问
        if (antPathMatcher.match("/**/auth/**", path) && StringUtils.isEmpty(userId)) {
            //必须登录 并且 用户还未登录
            return getVoidMono(response, ResultCodeEnum.LOGIN_AUTH);//适用于异步  异步弹出登录框时路径不变,包含auth
        }
        //同步登录时跳转页面 页面格式为xxx.html  但是xxx.html可能包含无需登录也能访问的路径,故采用枚举
        for (String u : urls) {//适合同步
            if (path.indexOf(u) != -1 && StringUtils.isEmpty(userId)) {//path中存在枚举的路径
                //路径要求必须登录 用户还未登录
                //重定向 响应码:303  响应头:重定向路径
                response.setStatusCode(HttpStatus.SEE_OTHER);
                try {
                    response.getHeaders().add(HttpHeaders.LOCATION,
                            REDIRECT_URL + URLEncoder.encode(url, "utf-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                //开始响应 直接跳出不经过放行
                return response.setComplete();
            }
        }
        //4 登录种类 ①ajax异步弹出登录窗口 ②windows.location.href同步到登录页面

        //传递真实用户id
        if (!StringUtils.isEmpty(userId)) {
            //错误 请求头不可更改 报错
//            request.getHeaders().add("userId",userId);
            //变异 mutate():原请求构建为新的请求
            request.mutate().header("userId", userId);
        }
        //传递临时用户id
        String userTempId = getUserTempId(request);
        if (!StringUtils.isEmpty(userTempId)) {
            request.mutate().header("userTempId", userTempId);
        }
        //5 放行
        return chain.filter(exchange);
    }

    //获取临时用户id 临时用户无缓存
    private String getUserTempId(ServerHttpRequest request) {
        //1 异步 从请求头中获取
        String userTempId = request.getHeaders().getFirst("userTempId");
        if (StringUtils.isEmpty(userTempId)) {
            //若没有则代表同步请求
            //2 同步 从Cookie获取
            HttpCookie httpCookie = request.getCookies().getFirst("userTempId");
            if (null != httpCookie) {
                userTempId = httpCookie.getValue();
            }
        }
        return userTempId;
    }

    //获取真实用户id
    private String getUserId(ServerHttpRequest request) {
        //1 获取token
        //1.1 异步 从请求头获取 为什么能从请求头获取 见request.js 12行
        String token = request.getHeaders().getFirst("token");
        if (StringUtils.isEmpty(token)) {
            //若没有则代表同步请求
            //1.2 同步 从Cookie获取
            HttpCookie httpCookie = request.getCookies().getFirst("token");
            if (null != httpCookie) {
                token = httpCookie.getValue();
            }
        }
        if (!StringUtils.isEmpty(token)) {
            //2 根据token去缓存中获取userId
            if (redisTemplate.hasKey(RedisConst.USER_LOGIN_KEY_PREFIX + token)) {
                return (String) redisTemplate.opsForValue().get(RedisConst.USER_LOGIN_KEY_PREFIX + token);
            }
        }
        return null;
    }

    //统一返回值
    private Mono<Void> getVoidMono(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result<Object> result = Result.build(null, resultCodeEnum);
        String json = JSONObject.toJSONString(result);
        DataBuffer dataBuffer = response.bufferFactory().wrap(json.getBytes());
        //设置响应的编码
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        return response.writeWith(Mono.just(dataBuffer));
    }

    //过滤的顺序 本过滤器是第四个执行,   优先级稍低的话不影响网关的处理,能使九大默认过滤器起作用
    @Override
    public int getOrder() {
        return 0;
    }
}
